「VirusTotal」でドメインの健全性を定期的に自動でチェックする(APIv3版)
はじめに
こんにちは。大阪オフィスの林です。
先日、下記のブログを書かせて頂きました。
前回はVirusTotalから提供されているAPIv2
を使って実装していたのですが、APIv2
だとドメインによっては結果がイイ感じに取得できなさそうだったので、同様にVirusTotalから提供されているAPIv3
を使って同じようなチェックをやってみました。
何がイイ感じに取れなかった???
下記はAPIv2
のドメインチェック実行結果の抜粋なのですが、かなり前の日時(下記の例だと2014年のレポート)まで引っ張って来てしまい、チェックしたいドメインに対して最新のレポートだけでチェックすることが困難ということが分かりました。(最新のレポートだけをチェックするという機能が無く、APIv2
でやるとなると結構作りこみが手間ということが分かりました。。。)
(省略) "detected_urls": [ { "url": "http://classmethod.net/[email protected]&method=post", "positives": 2, "total": 52, "scan_date": "2014-05-26 08:28:36" } ], (省略)
なお、WebUIでも同様のドメインチェックの機能が提供されているのですがWebUIからチェックを試すと、問題なし(Positivesが「0」)と判定されます。(APIv2
は過去のログも見てしまうため、上記のレポートのように「Positivesが0以外という判定をしてしまいます。」)
なぜ結果が異なるかというと、WebUIで実行した際にキックされるAPIはAPIv3
であり、そもそもの得られる結果や機能が違うからです。APIv3
は現在(2020年5月17日現在)Bata版という扱いですが、制約や制限などはAPIv2
と変わりが無く、VirusTotalではAPIv3
の使用が推奨されているということが分かったため、本記事ではAPIv3
での実装方法をまとめておきたいと思います。
Lambdaのコード作成(v2とv3との違い)
今回も「python3.7」を使ってコードを作成していきます。コードの全体は本記事の最後に載せておきますので、本項ではポイントとなる部分だけ説明します。
・使用したライブラリ(v2とv3との違い)
APIv2で実装していた時と比較し、コマンド実行のためにimport subprocess
とfrom subprocess import check_output
を追加しています。
import os #↓の2行を追加してる。 import subprocess from subprocess import check_output import json import boto3 import logging import requests import re import time from pytz import timezone from datetime import datetime
・環境変数の利用(v2とv3との違い)
APIv2
とAPIv3
で変わりはありません。
・ドメインのリストを取得(v2とv3との違い)
APIv2
とAPIv3
で変わりはありません。
・APIキック&レポートを取得(v2とv3との違い)
前回の記事では実行回数にフォーカスして説明をしておりましたが、実行するAPIのURLがhttps://www.virustotal.com/vtapi/v2/domain/report
からhttps://www.virustotal.com/api/v3/domains/
に変わっています。
・レポートのチェック&異常時通知(v2とv3との違い)
前回の記事ではリクエストの失敗
とpositivesの値
で異常の判定をしていました。
if '"response_code": 1' in response.text: if re.search('"positives": [^0]' , response.text): response = client.publish( TopicArn = snstopic, Message = json.dumps(j, indent=4), Subject = 'ドメイン名「' + domain + '」は怪しい' ) key = 'Log/' + Shapjst_now + '/BadDomain/' + domain + '.log' else: key = 'Log/' + Shapjst_now + '/GoodDomain/' + domain + '.log' else: (処理)
APIv3
では、last_analysis_stats
とmalicious
を見て異常の判定を行うこととなります。なおmalicious
の結果が上述したWebUIで見えた結果とリンクする数値となります。
if re.search('"last_analysis_stats"' , jsonresult): if re.search('"malicious": [^0]' , jsonresult): response = client.publish( TopicArn = snstopic, Message = jsonresult, Subject = 'ドメイン名「' + domain + '」は怪しい' ) key = 'Log/' + Shapjst_now + '/BadDomain/' + domain + '.log' else: key = 'Log/' + Shapjst_now + '/GoodDomain/' + domain + '.log' else:
・ログを格納
APIv2
とAPIv3
で変わりはありません。
まとめ
APIv2
とAPIv3
で得られる結果や実装方法が異なることが分かりました。またドメインチェックに関しては簡単に本記事で簡単に実装方法を紹介させて頂きました。今回は自分の備忘録的なまとめになりましたが何かしら参考になると幸いです。
以上、大阪オフィスの林がお送りしました!
コード
import os import subprocess from subprocess import check_output import json import boto3 import logging import requests import re import time from pytz import timezone from datetime import datetime def lambda_handler(event, context): logger = logging.getLogger() logger.setLevel(logging.INFO) apikey = os.environ['apikey'] snstopic = os.environ['snstopic'] bucket_name = os.environ['bucket_name'] domainlist = os.environ['domainlist'] s3 = boto3.client('s3') response = s3.get_object(Bucket=bucket_name, Key=domainlist) body = response['Body'].read() client = boto3.client('sns') bodystr = body.decode('utf-8') domains = bodystr.split("\n") i = 1 s3 = boto3.resource('s3') utc_now = datetime.now(timezone('UTC')) jst_now = utc_now.astimezone(timezone('Asia/Tokyo')) Shapjst_now = jst_now.strftime('%Y-%m-%d-%H-%M') for domain in domains: if i % 4 != 0: domain = domain.replace('\r', '') result = check_output(["curl","-s","https://www.virustotal.com/api/v3/domains/"+domain,"-H","x-apikey:"+apikey]) jsonresult = result.decode('utf8').replace("'", '"') if re.search('"last_analysis_stats"' , jsonresult): if re.search('"malicious": [^0]' , jsonresult): response = client.publish( TopicArn = snstopic, Message = jsonresult, Subject = 'ドメイン名「' + domain + '」は怪しい' ) key = 'Log/' + Shapjst_now + '/BadDomain/' + domain + '.log' else: key = 'Log/' + Shapjst_now + '/GoodDomain/' + domain + '.log' else: response = client.publish( TopicArn = snstopic, Message = jsonresult, Subject = 'ドメイン名「' + domain + '」はリクエスト失敗' ) key = 'Log/' + Shapjst_now + '/RequestfailedDomain/' + domain + '.log' obj = s3.Object(bucket_name,key) obj.put( Body = jsonresult ) i = i + 1 else: domain = domain.replace('\r', '') result = check_output(["curl","-s","https://www.virustotal.com/api/v3/domains/"+domain,"-H","x-apikey:"+apikey]) jsonresult = result.decode('utf8').replace("'", '"') if re.search('"last_analysis_stats"' , jsonresult): if re.search('"malicious": [^0]' , jsonresult): response = client.publish( TopicArn = snstopic, Message = jsonresult, Subject = 'ドメイン名「' + domain + '」は怪しい' ) key = 'Log/' + Shapjst_now + '/BadDomain/' + domain + '.log' else: key = 'Log/' + Shapjst_now + '/GoodDomain/' + domain + '.log' else: response = client.publish( TopicArn = snstopic, Message = jsonresult, Subject = 'ドメイン名「' + domain + '」はリクエスト失敗' ) key = 'Log/' + Shapjst_now + '/RequestfailedDomain/' + domain + '.log' obj = s3.Object(bucket_name,key) obj.put( Body = jsonresult ) i = i + 1 time.sleep(60)